in this video we're going to handle
simple spreadsheet animation in a pure
unity ECS game this is one of the
simplest forms of animation and it can
be done extremely fast so you can easily
render thousands of units let's begin
[Music]
hello and welcome I'm your code monkey
and this channel is all about helping
you learn how to make your own games
with NF tutorials made by a professional
indie game developer so if you find the
video helpful consider subscribing so
the new dot stack is wonderful and works
insanely fast but one of the biggest
challenges is animation as of right now
there is no implementation for the
default Unity animations so you have to
build your own system however you need
to be careful to make sure your
animation system doesn't become a
performance bottleneck
now when approached you can do is a mesh
based system that is what I normally use
in my videos where each unit has a mesh
and just modifies that mesh per
animation frame that's the approach I
took in the Marines vs Zombies video in
that scene each animation frame is a
different mesh and the system just grabs
the mesh that matches the current
animation frame that is a great approach
but also has some limitations and it's
quite complex to handle lots of
different meshes so here we're going to
create the simplest form of animation
just some basic spreadsheet animation so
let's begin by writing some code let's
start off by making a script to run our
code so a new C sharp script let's call
it the game hammer make a game object
and just write a script okay okay so
let's start off by just spawning a
simple entity and drawing it now first
of all if you have absolutely no idea
how ECS works then check out the getting
started video that videos should teach
you the core of how ECS works and how to
make an entity and add components in
here I'm just going to quickly write the
code to spawn our entity so here first
we create a entity archetype
so here we just create it very simple
with the translation component right so
that's our basic archetype now let's
create it alright that's it very simple
let's see for entities and they've been
created so here we are and here the
entity buzzer and indeed we have an
entity with translation okay good now
let's see how we can draw a sprite so
let's make a system that won't be
responsible for drawing it so let's put
it in a different script and let's call
this the sprite sheet renderer in here
this will be a component system and now
in here let's just cycle through all the
entities with the translation component
so we do entities for each we grab a
reference to the translation component
and here we're going to use graphics
draw a mesh in order to draw them onto
the screen now here you can see the
parameters that will require so
essentially we need a mesh and a
material so let's have those as fields
on the game and one now I'm only adding
them in here just for testing if you
were to use this in a complete game you
would obviously place all of these
assets in a more appropriate place but
for now this is great for testing let's
go into the editor and here on the game
handler we can use for the quad mesh we
can use the default quad and for the
walking sprite sheet material let's
create a new material in here okay so we
have our material let's drag our
reference okay
and now here back in our code we need to
access these fields from our spreadsheet
renderer so again this case and let's
make a static instance that we can
access
so we have a static instance that we set
on awake and then a function to get the
instance and through this instance we
won't be able to access these fields
again if you are using this code in a
proper game you would manage your
references in a better way we're just
doing it this way just for testing so we
can now go into the spreadsheet brendor
we draw the mesh then we use the quad
mesh for the position we use the
translation that value for the
quaternion we don't any rotation so
quaternion
identity and for the material we use
that material all right so here is our
nice function call we're using the
graphics API in order to draw mesh so
we're going to draw a simple quad using
our material so let's see if it works
any of here we are and there's our
spreadsheet being correctly displayed
all right so we have the basics for
drawing a sprite with a custom system
now obviously the sprite is meant to
only display one frame of the sprite
sheet and not the whole thing so let's
see how we can do that now the way we're
going to do is by using a custom shader
and passing a specific parameter to that
shader the shader won't then use that
parameter in order to display only a
single animation frame that way every
single unit will have the same mesh the
same material but different parameters
so let's see the shader code here is the
shader code it is slightly modified in
order to support GPU instancing
essentially so we can pass in different
values once having the same mesh in the
material now I must say I am NOT an
expert in shaders this change code here
is put together from several examples
that I found online so if you see
something in this shader that could be
done better please let me know in the
comments but the shader does work for
what we're trying to do now the key
elements are down here this is what
defines what properties we can modify so
you can see that we are receiving an
instance color and an instance UV and
down here we can access the instance
properties so we're going to pass in the
main text UV as an array of UV
coordinates each unit will calculate its
own coordinates and then the whole thing
is run in a single drop ok
so this is a shader now let's see how we
can pass in these values to this shader
so here we have our draw mesh code now
in here we're going to swap two things
first we're going to pass values to the
shader using a material property block
this is what actually contains the data
that we wish to send to the shader so in
here we can do set the vector Arang the
name ID is the name that we gave in the
shader so here we are it's the main text
UV then we pass in an array of valence
now in here this vector 4 is going to be
the UV coordinates so let's define up
here to see how it's set up
essentially we have a width a height a X
offset and a Y offset now these are all
normalized values so regardless of
texture size the value from 0 to 1 and
with these values we're essentially
going to display the entire texture so
we have the offsets both at 0 so we're
going to start drawing on the lower left
corner and the width and height is 1 so
the entire texture so we're going to
draw the whole thing so we create a new
vector 4 and we pass in first the width
and the height and so on then in here we
pass in this UV into our material
properly what all right so that's our
material setup now let's pass it into
our drama cone so here is the version of
the function that we want so first our
mesh ok then the position then the
rotation then the material then layer
then we need a camera submission parties
so for the camera let's pass in camera
domain for the sub mesh we don't have a
sub mesh so passing 0 and then finally
our material property block
all right so just like that we have our
code to pass in custom values into our
shader so let's see now before we test
we need to assign our correct shader so
here in the material let's go down into
our custom and use the instance shader
all right now let's test any of here we
are indeed we can still see our nice
spreadsheet and we can see the entire
spreadsheet now let's make sure it's
working by saying DV for a single frame
so let's go back now here in order to
display just a single frame essentially
we need to mess around with the width
and the offset so in this case we have
four frames of animation so what we do
is 1/4 that way we have the width of a
single frame of animation so this is
only takes now let's test any of there's
our first frame of animation now let's
test the second frame so in order to
have the second frame we have the same
width but we modify the UV offset
essentially you modify by the UV width x
in this case by 1 and yep there is our
entity displaying the second frame of
animation alright awesome so we can now
play around with the values we put in
the material property block in order to
display different frames in order to
have spreadsheet animation all we really
need is to change those UV values over
time so let's handle that now we're
going to do our animation timing in ecs
let's make a new script and call this
the sprite sheet animation and now in
here we're going to have two things
we're going to have a component to hold
all of the animation data and a system
to animate that data so first start off
with this simple component so component
won't be a public struct let's call this
the sprite sheet animation data and we
obviously implement I component that
so this component won't handle all the
data that the animation system will
require so in this case the first thing
we need is the current frame so we store
a public int for the current frame then
we need to know the total frame count
so if publican for the frame count this
will be how many frames total the
animation has then we also need to know
the time spent in this current frame so
it probably float for the frame timer
and finally a timer max to know when to
go to the next frame so again a public
float for the frame time max so as the
frame timer increases when it goes past
the timer max we increase the current
frame and as the current frame goes past
the frame count it resets back to zero
all right so this is all that we need
for a very simple animation now let's
have this component into our animation
archetype so here we are so we add with
translation and a type of animation that
now let's also set up our starting
values
so here we are setting our component
data for the translation let's arm off
at 0 0 and for the data we start off at
the current frame 0 we have a total of 4
frames of animation the frame timer
starts at 0 and for internal max of 0.1
so essentially we're going to play this
animation 10 frames per second right so
we have our component data all set up
now we need to make a system to actually
animate so let's go back into our script
and down here make a public class this
will be the sprite sheet animation
animate and will be a job component
system now in here we're going to create
a very simple job we want a job looping
through all the entities with our
animation data component so first it's
only struct for our job and will be an
eye job for each with the sprite sheet
animation tab okay so here we are
cycling through all of our entities with
animation data now in here we go into
the data in order to access the frame
timer and increase it by del time now
again in a job we cannot use time table
time directly so we need to store it out
here ok so we increase the frame timer
by the Delta time then we need to test
if the frame timer is past the frame
time or max
so when we are above the frame tunnel
max we reduce the frame timer by the
frame timer max and we increase the
current frame so we increase the current
frame and we get the remainder of the
frame count so as you go past the frame
count we look back into zero alright so
that's it right here we have a very
simple animation system that just deals
with this simple data so now let's
schedule our jump and only really need
this to pass in the Delta all right so
that's it and now in here we can also
add a very simple verse compound in
order to make it quite a bit faster okay
so let's see this job in action ok here
we are and nothing moves since we aren't
updating it yet but if we go and inspect
our entity there it is
you can see the frame timer increasing
with the Delta time and as it goes past
the frame tarmacs which is point 1 you
can see the current frame constantly
increasing all right so we have our
admission that are correctly working now
let's calculate the UV frame based on
that data so let's go into the regular
system and in here the first thing we
need to do is to make sure that our
renderer only runs after the animation
system so for that we can use the update
after test in the type of spreadsheet
animation system that way this system
only runs after the animate has done its
thing and now here on the at least for
each we grab translation then also grab
the animation data and then in here
let's calculate the UV based on the
animation done so for the width we have
1 divided by the total number of frames
so we can do that frame count the height
our sprite sheet is setup only
horizontally so we only add one and for
the X offset it will essentially be the
width multiplied by the spreadsheet
animation data dot current rent
so as the current frame increases we
shift what we show to the right all
right so that's it let's test any of
their it is we have our nice entity with
a very nice simple spreadsheet animation
awesome you can check the stats to see
that we only have one quad so two
triangles four vertices and here's our
nice unit alright so now that we have
the core working let's see how we can
improve upon it now one of the things we
can improve quite easily is regarding
this UV calculation so right now we're
doing it here inside a for each meaning
we're doing it on the main thread
sequentially for each unit so a much
better approach is to simply calculate
this once we are accounting the
animation so right here this way we're
going to benefit from multi-threading as
we calculate our UV so let's simply add
another field to our animation data so
in here a public vector for for our UV
and here in our job code after we modify
the current frame let's also calculate
the UV so let's copy this code from here
into here and we simply set the
spreadsheet animation down UV and here
this could be further improved by
caching the width and height and
everything that doesn't change but for
now this will work in too long where you
be insanely fast so now we are
calculating the UV inside our job so we
can go back into our River system and in
here we grab the UV directly from the
spreadsheet data also another piece of
data that we can't complete inside our
job in here if we check out the draw
mesh you can see that there are several
versions that work with a matrix we're
going to need matrices later on when we
use the instance method so let's see on
weight calculating that right now let's
go into our animation data and up here
we also store a public matrix for our
matrix so here on let's calculate the
matrix so for that we go into the
spreadsheet animation data dot matrix
and we calculated using this static
function to get the translation rotation
and scale so we need a position so that
means we need to go up here in order to
add the translation component to this
job
now in here we just need to pass in
translation doubt value what turned our
identity since we don't have rotation
and vector 3.14 our scape alright so
this we have our matrix being calculated
inside of this job which is running nice
multi-threaded code now just one last
thing we need to do in order to handle
sprite sorting order it's in here and
let's simply come late a43 for our
position so first we grab the normal
position then we offset the position
that Xen by the position that Y
multiplied by a small value and then
this position is what we use in there so
essentially we're offsetting it on the
set based on the Y this way we get
automatic sprite sorting Gordon so
sprites with a load Y won't be in front
of sprites with a higher Y so that's it
for calculating in here now we can go
back into our render system and here we
can now use the draw mesh function that
contains both UV and the matrix all
right that should do it
so essentially here on our render system
all we're doing is rendering our mesh so
all the work in terms of calculating the
UV the doing the animation or completing
the matrix all that is done in a nice
multi-threaded code in here before that
happens okay so let's run the code and
see and yep awesome here we have our
entity very nicely animated now let's
change our code to spawn thousands of
entities so here let's create that
so here we have the code spawning a
thousand entities
let's randomize their position and their
starting animation so we can see the
difference all right so here we have a
bunch of randomness so we can easily see
everything and let's test any of here we
have a thousand entities only using some
very nice spreadsheet animation let's
look at the stats and here we are a
thousand entities running in the CPU in
six milliseconds with 150 180 fps now
let's increase it up to 10,000 and right
here at 10,000 we can already see some
performance issues so let's see how we
can improve them here in a renderer we
can easily improve a bunch of things
first of all let's create the material
property block outside and then simply
reuse it that won't save a lot of
garbage calls and the same thing for our
unit vector
so here we are reusing our UV array ion
let's see how much we've improved so
here we are and we had quite a nice
improvement we went down from 50
milliseconds per frame down to 30
milliseconds per frame so right now we
have 10,000 units of them being
independently animated and everything is
running at 30 frames per second so this
right here is the simple way to do
spreadsheet animation and render it
there is a way that we can further
improve upon this and get even better
performance here in the code the main
bottleneck is right here on the drum
ashcombe essentially we're running
through this function for each entity so
we're doing this column as many entities
as we have so if we have 10,000 we have
10,000 counts however on the graphics
API we have another function and that is
the draw mesh instance this one allows
us to render multiple meshes in a single
function call so we can greatly improve
performance however that also contains a
lot of tricky and limitations and weird
things we must do it so we're going to
cover that in the next video so right
here we have built a nice simple
spreadsheet animation system we have in
here a component that handles all the
data that the spreadsheet requires then
we have a system which works on that
data in order to update the current
frame and also calculate the matrix and
the UV required to render it
this job is fully multi-threaded and
using burst so it's insanely fast and
then we have our renderer which takes on
that data and does a simple draw mesh
comp and here on the archetype you can
see that the only thing we need for this
whole system to work is just the
translation and the data component so
again this is a very simple way of doing
a simple spreadsheet animation whilst
using pure unity ECS stay tuned for the
next video where we're going to explore
the drama SH instance and see how much
better we can push this performance as
always you can download the project
files in utilities from in tcode
Montcalm if you liked the video
subscribe the channel for more unity
tutorials post any questions you have in
the comments and I'll do my best answer
alright see you next time
[Music]